/**
 * 
 */
package anyware.desktop.impl;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import javax.annotation.Resource;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.NumberTools;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.Directory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import anyware.desktop.DesktopException;
import anyware.desktop.DesktopService;
import anyware.desktop.dao.FileItemDao;
import anyware.desktop.model.FileItem;

/**
 * @author keke
 */
@Service("desktopService")
public class DefaultDesktopService implements DesktopService {

    private static final Logger LOG            =
                                                 LoggerFactory
                                                         .getLogger(DefaultDesktopService.class);
    private static final String SYNC_ROOTS     = "sync.roots";
    private static final String SYNCROOTS_FILE = "./conf/syncroots.properties";
    @Resource(name = "luceneDir")
    private Directory           directory;
    @Resource
    private FileItemDao<?>      fileItemDao;

    private IndexReader         indexReader;

    private IndexSearcher       indexSearcher;
    private QueryParser         queryParser;

    private Properties          syncRootProperties;

    public void addSyncRoot(final File root) throws IOException {
        Validate.notNull(root);
        final Collection<File> roots = getSyncRoots();
        if (roots.contains(root.getAbsoluteFile())) {
            // FIXME: we might need to notify caller about this
            // situation.
            return;
        }
        roots.add(root);
        storeSyncRootProperties(roots);
    }

    public Collection<File> getSyncRoots() throws IOException {
        final Properties properties = getSyncRootProperties();
        final List<File> roots = new ArrayList<File>();
        final String syncRootStr = properties.getProperty(SYNC_ROOTS, "");
        if (!StringUtils.isEmpty(syncRootStr)) {
            final String[] parts = syncRootStr.split(";");
            for (final String part : parts) {
                roots.add(new File(part));
            }
        }
        return roots;
    }

    @Transactional(readOnly = true)
    public Collection<FileItem> getToBeIndexedItems() {
        return fileItemDao.getDirtyItems();
    }

    @Override
    public void removeSyncRoot(final File root) throws IOException {
        Validate.notNull(root);
        final Collection<File> roots = getSyncRoots();
        roots.remove(root.getAbsoluteFile());
        storeSyncRootProperties(roots);
    }

    @Transactional(readOnly = false)
    public void saveItem(final FileItem item) {
        fileItemDao.save(item);
    }

    @Override
    public Iterator<FileItem> search(final String query, final int start,
            final int size) throws IOException, DesktopException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Search {}", query);
        }
        final long startTime = System.currentTimeMillis();
        final List<FileItem> list = new ArrayList<FileItem>();
        try {
            final Hits hits = getSearcher().search(queryParser.parse(query));
            final int end = Math.min(start + size, hits.length());
            for (int i = start; i < end; i++) {
                final Document doc = hits.doc(i);
                final Long id =
                                NumberTools.stringToLong(doc.getField("id")
                                        .stringValue());
                list.add(fileItemDao.get(id));
            }
            if (LOG.isInfoEnabled()) {
                LOG.info("Search {} cost {}", new Object[] { query,
                        System.currentTimeMillis() - startTime });
            }
            return list.iterator();
        } catch (final ParseException e) {
            throw new DesktopException(e);
        }
    }

    public void setDirectory(final Directory directory) {
        this.directory = directory;
    }

    public void setFileItemDao(final FileItemDao<?> fileItemDao) {
        this.fileItemDao = fileItemDao;
    }

    public void setQueryParser(final QueryParser queryParser) {
        this.queryParser = queryParser;
    }

    /*
     * (non-Javadoc)
     * 
     * @see anyware.desktop.DesktopService#syncFile(java.io.File)
     */
    @Transactional(readOnly = false)
    public FileItem syncFile(final File file) {
        Validate.notNull(file);
        Validate.isTrue(file.exists() && file.isFile());
        FileItem fileItem = fileItemDao.getByPath(file.toURI().toString());
        if (fileItem == null) {
            fileItem = new FileItem(file);
        } else if (!fileItem.getInfo().isDirty()) {
            // file was modified?
            fileItem.getInfo().setDirty(
                    fileItem.getInfo().getLastModified().getTime() != file
                            .lastModified());
        }
        fileItem.getInfo().setLastAccessed(new Date());
        fileItemDao.save(fileItem);
        return fileItem;
    }

    private synchronized IndexSearcher getSearcher() throws IOException {
        if (indexReader == null) {
            indexReader = IndexReader.open(directory);
        } else if (!indexReader.isCurrent()) {
            final IndexReader newReader = indexReader.reopen();
            if (newReader != indexReader) {
                indexSearcher.close();
                indexSearcher = null;
                indexReader.close();
                indexReader = newReader;
            }
        }
        if (indexSearcher == null) {
            indexSearcher = new IndexSearcher(indexReader);
        }
        return indexSearcher;
    }

    private synchronized Properties getSyncRootProperties() throws IOException {
        if (syncRootProperties == null) {
            syncRootProperties = new Properties();
            final File file = new File(SYNCROOTS_FILE);
            if (!file.exists()) {
                file.createNewFile();
            }
            syncRootProperties.load(new FileReader(SYNCROOTS_FILE));
        }
        return syncRootProperties;
    }

    private void storeSyncRootProperties(final Collection<File> roots)
            throws IOException {
        final Properties properties = getSyncRootProperties();
        final StringBuilder sb = new StringBuilder();
        boolean first = true;
        for (final File file : roots) {
            if (!first) {
                sb.append(';');
            }
            sb.append(file.getAbsolutePath());
            first = false;
        }
        getSyncRootProperties().setProperty(SYNC_ROOTS, sb.toString());
        properties.store(new FileWriter(SYNCROOTS_FILE), "Update at "
                + new Date());
    }
}
